# ifndef __COMBase_H_
# define __COMBase_H_

bool AreCOMClassesRegistered ();
void RegisterCOMClasses ();
void UnregisterCOMClasses ();

class CRegisterClassFactories {
  public:
	CRegisterClassFactories ();
	~CRegisterClassFactories ();
};

////////////////////////////////////////////////////

typedef IUnknown * (* pfCreateCOMObject_t) ();

class CCOMClass {
	CCOMClass (const CCOMClass &);
	void operator = (const CCOMClass &);

  public:
	CCOMClass (const char * _strProgId, const char * _strCLSID, pfCreateCOMObject_t _pfCreateCOMObject, UINT _regcls);

  private:
	friend void RegisterCOMClasses ();
	friend void UnregisterCOMClasses ();
	friend class CRegisterClassFactories;

	const char * const m_strProgId;
	const char * const m_strCLSID;
	pfCreateCOMObject_t m_pfCreateCOMObject;
	const UINT m_regcls;

	DWORD m_dwRegisterClassObject;

	static CCOMClass * s_pFirst;
	CCOMClass * m_pNext;
};

# define DECLARE_COM_CLASS(_strProgID, _strCLSID, _T, _regcls) \
	IUnknown * Create_##_T () { \
		_T * const pT = new _T; \
		IUnknown * pUnk = NULL; \
		pT->QueryInterface (IID_IUnknown, reinterpret_cast<void **> (& pUnk)); \
		return pUnk; \
	} \
	CCOMClass Class_##_T (_strProgID, _strCLSID, Create_##_T, _regcls);

////////////////////////////////////////////////////

// Base implementation of reference counting.
class CComBase {
  public:
	CComBase () : m_lRefs (0) {}
	virtual ~CComBase () {}

	ULONG AddRef () {
		return ::InterlockedIncrement (& m_lRefs);
	}

	ULONG Release () {
		const LONG lRefs = ::InterlockedDecrement (& m_lRefs);
		if (lRefs == 0)
			delete this;
		return lRefs;
	}

  private:
	LONG m_lRefs;
};

// Implementation of v-table interface.
template <typename _Itf>
class CComVtblInterface : public CComBase, public _Itf {
  public:
	// IUnknown.
	STDMETHOD_ (ULONG, AddRef)  () {return CComBase::AddRef ();}
	STDMETHOD_ (ULONG, Release) () {return CComBase::Release ();}
	STDMETHOD (QueryInterface) (REFIID _iid, void ** _ppvObject) {
		if (::IsEqualIID (_iid, __uuidof (_Itf)))
			* _ppvObject = static_cast<_Itf *> (this);
		else if (::IsEqualIID (_iid, IID_IUnknown))
			* _ppvObject = static_cast<IUnknown *> (this);
		else {
			* _ppvObject = NULL;
			return E_NOINTERFACE;
		}
		AddRef ();
		return S_OK;
	}
};

void LoadTypeInfo (
	const GUID & _libid, int _iVerMajor, int _iVerMinor,
	const IID & _iid,
	ITypeInfo ** _ret
);

// Implementation of dual interface based on typelib.
template <typename _Itf, const GUID * _pLIBID, int _iLibVerMajor = 1, int _iLibVerMinor = 0>
class CComDualInterface : public CComBase, public _Itf {
  public:
	CComDualInterface () {
		LoadTypeInfo (* _pLIBID, _iLibVerMajor, _iLibVerMinor, __uuidof (_Itf), & m_pTypeInfo);
	}

	// IUnknown.
	STDMETHOD_ (ULONG, AddRef)  () {return CComBase::AddRef ();}
	STDMETHOD_ (ULONG, Release) () {return CComBase::Release ();}
	STDMETHOD (QueryInterface) (REFIID _iid, void ** _ppvObject) {
		if (::IsEqualIID (_iid, __uuidof (_Itf)))
			* _ppvObject = static_cast<_Itf      *> (this);
		else if (::IsEqualIID (_iid, IID_IDispatch))
			* _ppvObject = static_cast<IDispatch *> (this);
		else if (::IsEqualIID (_iid, IID_IUnknown))
			* _ppvObject = static_cast<IUnknown  *> (this);
		else {
			* _ppvObject = NULL;
			return E_NOINTERFACE;
		}
		AddRef ();
		return S_OK;
	}

	// IDispatch
	STDMETHOD (GetTypeInfoCount) (UINT * _ret) {
		* _ret = m_pTypeInfo ? 1 : 0;
		return S_OK;
	}
	STDMETHOD (GetTypeInfo) (UINT _uiIndex, LCID _lcid, ITypeInfo ** _ret) {
		* _ret = NULL;
		if (_uiIndex != 0)
			return DISP_E_BADINDEX;
		return m_pTypeInfo.CopyTo (_ret);
	}
	STDMETHOD (GetIDsOfNames) (REFIID _iid, LPOLESTR * _rgszNames, UINT _cNames, LCID _lcid, DISPID * _rgdispid) {
		return m_pTypeInfo->GetIDsOfNames (_rgszNames, _cNames, _rgdispid);
	}
	STDMETHOD (Invoke) (
		DISPID _dispidMember, REFIID _iid,
		LCID _lcid, WORD _wFlags, DISPPARAMS * _pDispParams, VARIANT * _pvarResult,
		EXCEPINFO * _pEI, UINT * _puArgErr
	) {
		return m_pTypeInfo->Invoke (
			static_cast<_Itf *> (this),
			_dispidMember, _wFlags, _pDispParams, _pvarResult,
			_pEI, _puArgErr
		);
	}

  private:
	CComPtr<ITypeInfo> m_pTypeInfo;
};

// Implementation of IConnectionPointContainer for single outgoing interface and single sink.
template <typename _EventsItf>
class CComConnectionPoint :
	public IConnectionPoint,
	public IConnectionPointContainer,
	public IEnumConnections
{
  public:
	// IUnknown
	STDMETHOD (QueryInterface) (REFIID _iid, void ** _ppvObject) {
		if (::IsEqualIID (_iid, IID_IConnectionPoint))
			* _ppvObject = static_cast<IConnectionPoint *> (this);
		else if (::IsEqualIID (_iid, IID_IConnectionPointContainer))
			* _ppvObject = static_cast<IConnectionPointContainer *> (this);
		else if (::IsEqualIID (_iid, IID_IEnumConnections))
			* _ppvObject = static_cast<IEnumConnections *> (this);
		else {
			* _ppvObject = NULL;
			return E_NOINTERFACE;
		}
		static_cast<IConnectionPoint *> (this)->AddRef ();
		return S_OK;
	}

	// IConnectionPointContainer
	STDMETHOD (EnumConnectionPoints) (IEnumConnectionPoints ** _ppEnum) {
		* _ppEnum = NULL;
		return E_NOTIMPL;
	}
	STDMETHOD (FindConnectionPoint) (REFIID _iid, IConnectionPoint ** _ppCP) {
		if (::IsEqualIID (_iid, __uuidof (_EventsItf)))
			return QueryInterface (IID_IConnectionPoint, reinterpret_cast<void **> (_ppCP));
		return CONNECT_E_NOCONNECTION;
	}

	// IConnectionPoint
	STDMETHOD (Advise) (IUnknown * _pUnk, DWORD * _pdwCookie) {
		if (m_pSink)
			return CONNECT_E_ADVISELIMIT;
		if (! _pUnk || ! _pdwCookie)
			return E_POINTER;
		* _pdwCookie = 0;
		return _pUnk->QueryInterface (& m_pSink);
	}
	STDMETHOD (Unadvise) (DWORD _dwCookie) {
		if (_dwCookie != 0)
			return CONNECT_E_NOCONNECTION;
		m_pSink.Release ();
		return S_OK;
	}
	STDMETHOD (GetConnectionInterface) (IID * _pIID) {
		::memcpy (_pIID, & __uuidof (_EventsItf), sizeof (IID));
		return S_OK;
	}
	STDMETHOD (GetConnectionPointContainer) (IConnectionPointContainer ** _ppCPC) {
		return QueryInterface (IID_IConnectionPointContainer, reinterpret_cast<void **> (_ppCPC));
	}
	STDMETHOD (EnumConnections) (IEnumConnections ** _ppEnum) {
		return QueryInterface (IID_IEnumConnections, reinterpret_cast<void **> (_ppEnum));
	}

	// IEnumConnections
	STDMETHOD (Next) (ULONG _cConnections, CONNECTDATA * _pCD, ULONG * _pulFetched) {
		if (m_pSink && _cConnections >= 1) {
			CONNECTDATA & cd = _pCD [0];
			m_pSink.QueryInterface (& cd.pUnk);
			cd.dwCookie = 0;
			* _pulFetched = 1;
		} else {
			* _pulFetched = 0;
		}
		return * _pulFetched == _cConnections ? S_OK : S_FALSE;
	}
	STDMETHOD (Skip) (ULONG _cConnections) {return E_NOTIMPL;}
	STDMETHOD (Reset) () {return E_NOTIMPL;}
	STDMETHOD (Clone) (IEnumConnections ** _ppEnum) {
		* _ppEnum = NULL;
		return E_NOTIMPL;
	}

	CComPtr<_EventsItf> m_pSink;
};

template<typename _T>
class CEnumString : public CComVtblInterface<IEnumString> {
  public:
	CEnumString (_T & _strings) {
		m_strings.swap (_strings);
		m_i = m_strings.begin ();
	}

	// IEnumString.
	virtual HRESULT STDMETHODCALLTYPE Next (ULONG _cElt, LPOLESTR * _rgelt, ULONG * _pcEltFetched) {
		::memset (_rgelt, 0, _cElt*sizeof (LPOLESTR));
		* _pcEltFetched = 0;

		for (size_t c = 0; c < _cElt; ++ c) {
			if (m_i == m_strings.end ())
				break;

			const string_t & str = * m_i;
			const size_t cWideChars = str.length ()*2 + 1;
			wchar_t * const pStr = (wchar_t *) ::CoTaskMemAlloc (cWideChars*sizeof (wchar_t));
			const size_t cLen = ::MultiByteToWideChar (CP_ACP, 0, str.c_str (), str.length (), pStr, cWideChars);
			pStr [cLen] = '\0';

			* _rgelt = pStr;
			++ m_i;
			++ * _pcEltFetched;
		}

		return * _pcEltFetched == _cElt ? S_OK : S_FALSE;
	}
	virtual HRESULT STDMETHODCALLTYPE Skip (ULONG _cElt) {
		while (_cElt -- != 0) {
			if (m_i == m_strings.end ())
				return S_FALSE;
			++ m_i;
		}
		return S_OK;
	}
	virtual HRESULT STDMETHODCALLTYPE Reset () {
		m_i = m_strings.begin ();
		return S_OK;
	}
	virtual HRESULT STDMETHODCALLTYPE Clone (IEnumString ** _ppEnum) {return E_NOTIMPL;}

  private:
	_T m_strings;
	_T::const_iterator m_i;
};

# endif // __COMBase_H_